<?php
use frontend\modules\yii2\widgets\layouts\markdown\Markdown;

$this->title = 'Log组件的学习和使用-Components组件';
$this->registerMetaTag([
    'name' => 'keywords',
    'content' => 'yii,yii2,框架,应用组件,Components,使用,教程,log'
]);
$this->registerMetaTag([
    'name' => 'description',
    'content' => '日志系统在整个生命周期中担任了非常重要的作用，是我们开发中必不可少调试工具，它能记录系统在运行时的状态和数据。'
]);
$this->registerMetaTag([
    'name' => 'author',
    'content' => 'chenzhiwei'
]);


$html = [
    "概述" => <<<HTML
# Log组件的学习和使用
日志系统在整个生命周期中担任了非常重要的作用，是我们开发中必不可少调试工具，它能记录系统在运行时的状态和数据。Yii提供的日志系统也格外强大，从`vendor/yiisoft/yii2/log/`目录下可知Yii一共提供了四种日志类型：
- [FileTarget](/docs/components/log/file)：将日志记录到磁盘文件（最常用）；
- [DbTarget](/docs/components/log/db)：将日志记录到数据库中（主要用于后台网页日志报表观测）；
- [EmailTarget](/docs/components/log/email)：将日志发送到指定邮箱（主要用于重大系统异常，需要马上告知运维的情况）；
- [SyslogTarget](/docs/components/log/syslog)：将日志记录到操作系统日志中（并不常用）；

HTML,
    "config" => <<<HTML
## 配置举例 id=config
日志的配置方式大同小异，举例如下：
```php
[
    'components' => [
        'log' => [
            'flushInterval' => 1,
            'traceLevel' => 0,
            'targets' => [
                'file' => [
                    'logVars' => [],
                    'class' => 'yii\log\FileTarget',
                    'levels' => ['info'],
                    'categories' => ['login'],
                    'logFile' => '@frontend/runtime/logs/login/'.date("Y-m-d").'.log',
                    'maxFileSize' => 1024 * 100, //100M
                    'maxLogFiles' => 100,
                    'exportInterval' => 1,
                    'prefix' => function() {   //日志格式自定义 回调方法
                        return "";
                    },
                ],
                'email' => [
                    'class' => 'yii\log\EmailTarget',
                    'levels' => ['error', 'warning'],
                    'message' => [
                        'to' => 'admin@example.com',
                    ],
                ],
            ],
        ],
    ],
]
```
你需要注意下面两个参数：
- `flushInterval`：可以存放在内存中的日志数量  
因为写日志到文件或者数据库都是IO操作，非常慢，为了提高性能，Yii会把日志先存放到内存中，等请求完成后或者日志存满`flushInterval`条（默认1000条）后才会写日志到文件中（或其他操作）。这就是为什么我们明明执行完了代码段，系统也没报错，但是日志文件中却什么都没有的原因。    
每个`Target`都有一个`exportInterval`参数，也是用于控制输出的，默认为记录满1000条后输出到文件，因此如果你想要让日志即刻写到文件，可以把`flushInterval`和`exportInterval`都设置为1：
```php
Yii::\$app->log->flushInterval = 1;
Yii::\$app->log->targets["file"]->exportInterval = 1;
```
> 注意：`exportInterval`优先级大于`flushInterval`，因此内存日志量小于`exportInterval`时，即使`flushInterval`达到要求也无法将日志写到文件。

- `traceLevel`：是否输出调试信息  
如果设置为0，则输出如下：
```php
Yii::info("traceLevel = 0", "login");
// 日志内容：
// 2021-12-27 13:31:39 [172.18.0.1][-][-][info][login] traceLevel = 0
```
如果设置为调试等级，则会输出调试信息：
```php
Yii::info("traceLevel = 3", "login");
// 日志内容：
// 2021-12-27 13:33:30 [172.18.0.1][-][-][info][login] traceLevel = 0
//     in /var/www/html/cms/frontend/modules/yii2/views/docs/components-log.php:99
//     in /var/www/html/cms/frontend/modules/yii2/controllers/DocsController.php:96
```

HTML,
    "common" => <<<HTML
## Target通用配置 id=common
在4种日志类型中，有很多配置都是一样的，因为它们都是继承于`yii\log\Target`，我们在这里集中介绍。

HTML,
    "common.levels" => <<<HTML
### levels id=levels
设置日志的消息等级，一共有如下5级：
- `error`：
```php
Yii::error(message, category);
```
- `warning`：
```php
Yii::warning(message, category);
```
- `info`：
```php
Yii::info(message, category);
```
- `trace`：
```php
Yii::trace(message, category);
```
- `profile`：
```php
Yii::profile(message, category);
```

消息等级会体现在日志中，如：
```shell
2021-12-29 09:34:08 [info][login] info等级
2021-12-29 09:34:08 [error][login] error等级
2021-12-29 09:34:08 [warning][login] warning等级
2021-12-29 09:34:08 [trace][login] trace等级
2021-12-29 09:34:08 [profile][login] profile等级
```


HTML,
    'common.logVars' => <<<HTML
### logVars id=logVars
指定打印全局变量的内容到日志，这个属性支持的全局变量如下：
```php
public \$logVars = [
    '_GET',
    '_POST',
    '_FILES',
    '_COOKIE',
    '_SESSION',
    '_SERVER',
];
```
接下来我们对`\$logVars`进行不同的配置，看看有什么效果，假设登录日志配置如下：
```php
[
    'components' => [
        'log' => [
            'flushInterval' => 1,
            'traceLevel' => 0,
            'targets' => [
                'login' => [
                    'logVars' => [],
                    'class' => 'yii\log\FileTarget',
                    'levels' => ['info'],
                    'categories' => ['login'],
                    'logFile' => '@frontend/runtime/logs/login/'.date("Y-m-d").'.log',
                    'exportInterval' => 1,
                ],
            ],
        ],
    ],
]
```
HTML,
    "不记录全局变量" => <<<HTML
#### 不记录全局变量 id=logVars-none
在大多数情况下我们并不需要记录全局变量，此时给`logVars`赋值为空即可：
```php
'logVars' => [],
```
当`logVars`赋值为空，日志信息只记录我们自定义的日志：
```php
Yii::info("只记录这里的内容到日志文件", "login");

// 日志文件内容如下：
2021-12-28 06:15:48 [172.23.0.1][-][-][info][login] 只记录这里的内容到日志文件
```
HTML,
    "记录所有全局变量" => <<<HTML
#### 记录所有全局变量 id=logVars-all
你如果不设置`logVars`属性，那默认将会记录所有上述的全局遍历到日志：
```php
Yii::info("记录所有全局变量", "login");

// 日志内容如下：
2021-12-28 05:50:06 [172.23.0.1][-][-][info][login] 记录所有全局变量
2021-12-28 05:50:06 [172.23.0.1][-][-][info][application] \$_GET = [
    'level1' => 'components'
    'level2' => 'log'
]

\$_POST = []

\$_FILES = []

\$_COOKIE = [
    '_identity-frontend' => '50265c005e66fe4d08b7e0fd181a1a5a5abadbedcd931dc06942d04d36980feca:2:{i:0;s:18:\"_identity-frontend\";i:1;s:46:\"[1,\"24-dfaF7ZwYoeCm5Qx4XCYhglXpWW2vb\",2592000]\";}'
    'advanced-frontend' => '5pbdkao96thpoig90uacndkvj8'
    '_csrf-frontend' => '91c27c0f8fd7999f742e481d44c6f3652acf56516ea9bb3c011447cf33265376a:2:{i:0;s:14:\"_csrf-frontend\";i:1;s:32:\"cVJKWOIkWvsFKr8dtwdzc-Wql2hKW-h2\";}'
]

\$_SERVER = [
    'REDIRECT_STATUS' => '200'
    'HTTP_HOST' => '127.0.0.1:20080'
    'HTTP_CONNECTION' => 'keep-alive'
    'HTTP_CACHE_CONTROL' => 'max-age=0'
    'HTTP_SEC_CH_UA' => '\"Chromium\";v=\"92\", \" Not A;Brand\";v=\"99\", \"Google Chrome\";v=\"92\"'
    'HTTP_SEC_CH_UA_MOBILE' => '?0'
    'HTTP_UPGRADE_INSECURE_REQUESTS' => '1'
    'HTTP_USER_AGENT' => 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36'
    'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9'
    'HTTP_SEC_FETCH_SITE' => 'same-origin'
    'HTTP_SEC_FETCH_MODE' => 'navigate'
    'HTTP_SEC_FETCH_USER' => '?1'
    'HTTP_SEC_FETCH_DEST' => 'document'
    'HTTP_REFERER' => 'http://127.0.0.1:20080/docs/components'
    'HTTP_ACCEPT_ENCODING' => 'gzip, deflate, br'
    'HTTP_ACCEPT_LANGUAGE' => 'zh-CN,zh;q=0.9'
    'HTTP_COOKIE' => '_identity-frontend=50265c005e66fe4d08b7e0fd181a1a5a5abadbedcd931dc06942d04d36980feca%3A2%3A%7Bi%3A0%3Bs%3A18%3A%22_identity-frontend%22%3Bi%3A1%3Bs%3A46%3A%22%5B1%2C%2224-dfaF7ZwYoeCm5Qx4XCYhglXpWW2vb%22%2C2592000%5D%22%3B%7D; advanced-frontend=5pbdkao96thpoig90uacndkvj8; _csrf-frontend=91c27c0f8fd7999f742e481d44c6f3652acf56516ea9bb3c011447cf33265376a%3A2%3A%7Bi%3A0%3Bs%3A14%3A%22_csrf-frontend%22%3Bi%3A1%3Bs%3A32%3A%22cVJKWOIkWvsFKr8dtwdzc-Wql2hKW-h2%22%3B%7D'
    'PATH' => '/app:/app/vendor/bin:/root/.composer/vendor/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
    'SERVER_SIGNATURE' => '<address>Apache/2.4.38 (Debian) Server at 127.0.0.1 Port 20080</address>
'
    'SERVER_SOFTWARE' => 'Apache/2.4.38 (Debian)'
    'SERVER_NAME' => '127.0.0.1'
    'SERVER_ADDR' => '172.23.0.4'
    'SERVER_PORT' => '20080'
    'REMOTE_ADDR' => '172.23.0.1'
    'DOCUMENT_ROOT' => '/app/frontend/web'
    'REQUEST_SCHEME' => 'http'
    'CONTEXT_PREFIX' => ''
    'CONTEXT_DOCUMENT_ROOT' => '/app/frontend/web'
    'SERVER_ADMIN' => 'webmaster@localhost'
    'SCRIPT_FILENAME' => '/app/frontend/web/index.php'
    'REMOTE_PORT' => '35368'
    'REDIRECT_URL' => '/docs/components/log'
    'GATEWAY_INTERFACE' => 'CGI/1.1'
    'SERVER_PROTOCOL' => 'HTTP/1.1'
    'REQUEST_METHOD' => 'GET'
    'QUERY_STRING' => ''
    'REQUEST_URI' => '/docs/components/log'
    'SCRIPT_NAME' => '/index.php'
    'PATH_INFO' => '/docs/components/log'
    'PATH_TRANSLATED' => 'redirect:/index.php/docs/components/log/components/log'
    'PHP_SELF' => '/index.php/docs/components/log'
    'REQUEST_TIME_FLOAT' => 1640670606.5267
    'REQUEST_TIME' => 1640670606
    'argv' => []
    'argc' => 0
]

```



它默认会让上述数组中的系统变量值加入到日志中，当然你也可以在在配置里覆盖，如：
```php
// 上述所有系统变量的值都不记录
Yii::\$app->log->targets["file"]->logVars = [];

// 只记录GET和POST
Yii::\$app->log->targets["file"]->logVars = ['_GET', '_POST'];
```
一般的做法是把它设置为空数组，因为大多数情况下我们已经将感兴趣的日志内容传入到日志系统中，而且每条日志都只占用一行（便于分析），因为系统变量的内容太多，而且大多没有用，反而会影响日志的分析。

HTML,
    "记录指定全局变量" => <<<HTML
#### 记录指定全局变量 id=logVars-some
如果你只对`\$_GET`和`\$_POST`的值感兴趣，则可以作如下配置：
```php
'logVars' => ['_GET', '_POST']
```
此时只会打印`\$_GET`和`\$_POST`的值到日志文件：
```php
Yii::info("记录指定全局变量", "login");

// 日志文件如下：
2021-12-28 05:55:26 [172.23.0.1][-][-][info][application] \$_GET = [
    'level1' => 'components'
    'level2' => 'log'
]

\$_POST = []

```
HTML,
    "只记录全局变量的某些值" => <<<HTML
#### 全局变量过滤 id=logVars-filter
`logVars`也支持对全局变量的值进行过滤，你可以只记录感兴趣的值，比如：
```php
'logVars' => ['_SERVER.HTTP_HOST', '_COOKIE', '!_COOKIE._csrf-frontend'],
```
此时只会将`\$_SERVER['HTTP_HOST']`的值和除了`\$_COOKIE['_csrf-frontend']`之外的所有`\$_COOKIE`记录到日志中，通过此方法可以过滤掉大量占空间又没用的数据：
```php
Yii::info("内容过滤", "login");

// 日志内容
2021-12-28 06:04:29 [172.23.0.1][-][-][info][login] 内容过滤
2021-12-28 06:04:29 [172.23.0.1][-][-][info][application] \$_SERVER = [
    'HTTP_HOST' => '127.0.0.1:20080'
]

\$_COOKIE = [
    '_identity-frontend' => '50265c005e66fe4d08b7e0fd181a1a5a5abadbedcd931dc06942d04d36980feca:2:{i:0;s:18:\"_identity-frontend\";i:1;s:46:\"[1,\"24-dfaF7ZwYoeCm5Qx4XCYhglXpWW2vb\",2592000]\";}'
    'advanced-frontend' => '5pbdkao96thpoig90uacndkvj8'
]
```
HTML,
    'common.categories' => <<<HTML
### categories id=categories
这个属性很重要，是必填项，它用于锁定目标日志，比如说如下配置：
```php
[
    'components' => [
        'log' => [
            'flushInterval' => 1,
            'traceLevel' => 0,
            'targets' => [
                [
                    'class' => 'yii\log\FileTarget',
                    'categories' => ['create_user'],
                    'logFile' => '@frontend/runtime/logs/user/create.log',
                ],
                [
                    'class' => 'yii\log\FileTarget',
                    'categories' => ['delete_user'],
                    'logFile' => '@frontend/runtime/logs/user/delete.log',
                ]
            ],
        ],
    ],
]
```
使用举例：
```php
// 将日志记录到create.log中
Yii::info("a new user has been created", "create_user");

// 将日志记录到delete.log中
Yii::info("user has been deleted", "delete_user");
```

高级用法还支持通配符`*`，比如说：
```php
[
    'components' => [
        'log' => [
            'flushInterval' => 1,
            'traceLevel' => 0,
            'targets' => [
                [
                    'logVars' => [],
                    'class' => 'yii\log\FileTarget',
                    'levels' => ['error'],
                    'categories' => ['yii\web\HttpException:*'],
                    'except' => ['yii\web\HttpException:404'],
                    'logFile' => '@frontend/runtime/logs/error/'.date("Y-m-d").'.log',
                    'exportInterval' => 1,
                ],
            ],
        ],
    ],
]
```
日志系统会过滤系统抛出的异常，如果是`yii\web\HttpException`产生的异常则会记录到`logFile`目录下的文件中，抛出的异常不需要指定`catagories`，因为系统已经默认集成：
```php
throw new \yii\web\HttpException("log test");
```
HTML,
    "common.prefix" => <<<HTML
### prefix id=prefix
#### 默认配置 id=preifx-default
自定义日志前缀，如果你没有设置这个属性的话，默认会调用`getMessagePrefix()`将`user IP`、`user ID`和`session ID`作为日志前缀输出：
```php
//                  [ user IP  ][userID][        session ID        ]
2021-12-28 06:04:29 [172.23.0.1][lvzmen][2i3nevq6pkpqg1ub0v8lodbc4o][info][login] 前缀演示
```
如果日志在没有检测到登录状态，则会用`-`表示，比如你在`console`控制台执行的日志：
```php
2021-12-28 06:04:29 [127.0.0.1][-][-][info][login] 控制台执行的日志
```
#### 自定义前缀 id=prefix-custom
可以返回空字符串，取消前缀设置：
```php
[
    'logVars' => [],
    'class' => 'yii\log\FileTarget',
    'levels' => ['info'],
    'categories' => ['login'],
    'logFile' => '@frontend/runtime/logs/login/'.date("Y-m-d").'.log',
    'exportInterval' => 1,
    'prefix' => function() {   //日志格式自定义 回调方法
        return "";
    },
]
```
上述配置的效果如下：
```php
2021-12-28 06:04:29 [info][login] 没有设置前缀的日志
```
你也可以设置感兴趣的值：
```php
'prefix' => function() {   //日志格式自定义 回调方法
    return "Hello World";
},
```
效果如下：
```php
2021-12-29 09:34:08 Hello World[info][login] 自定义前缀文本
```

HTML,
    "common.exportInterval" => <<<HTML
### exportInterval id=exportInterval
这个属性指示了在导出日志前可以允许多少条日志存储在内存中，默认是1000条。这个属性的意义和`flushInterval`类似，但是优先级高于`flushInterval`，其作用是避免高频率的IO操作，提升性能。有两个特殊的值需要注意：
- `0`：表示不在内存中设置上限，在请求结束后才全部将内存中的日志一次性导出；
- `1`：表示不在内存中驻留，马上将日志导出。

HTML,
    "common.microtime" => <<<HTML
### microtime id=microtime
如果你的日志要求时间比较严格，秒级的时间无法满足调试要求，那可以设置这个属性为`true`，启动微秒级的时间记录：
```php
[
    'logVars' => [],
    'class' => 'yii\log\FileTarget',
    'levels' => ['info'],
    'categories' => ['login'],
    'logFile' => '@frontend/runtime/logs/login/'.date("Y-m-d").'.log',
    'exportInterval' => 1,
    'microtime' => true, // 微妙
    'prefix' => function() {   //日志格式自定义 回调方法
        return "";
    },
]
```
日志输出如下：
```php
2021-12-29 13:52:38.485944 [info][login] 记录微秒级的时间
```
HTML,
    "common.enabled" => <<<HTML
### enabled id=enabled
使能日志记录功能，可以设置为`boolean`，也可以设置为回调函数。这个功能在特殊的情况下很有用，比如说你只想记录某个用户的数据，则可以写如下配置：
```php
[
    'logVars' => [],
    'class' => 'yii\log\FileTarget',
    'categories' => ['login'],
    'logFile' => '@frontend/runtime/logs/login/'.date("Y-m-d").'.log',
    'exportInterval' => 1,
    'prefix' => function() {
        return "";
    },
    'enabled' => function(){
        // 只有在登录的情况下且用户是lvzmen才使能日志记录功能
        return Yii::\$app->user->isGuest ? false : Yii::\$app->user->identity->username == "lvzmen";
    }
]
```
上述日志设置为只记录`lvzmen`用户的数据，可以有效过滤掉其他用户的数据：
```php
// test 用户执行如下代码不会记录日志
Yii::info("我是test，不会记录任何日志", "login");

// lvzmen 用户执行如下代码会记录日志
Yii::info("我是lvzmen，记录日志成功", "login");
```
HTML,
    "" => <<<HTML

HTML,
];

// Yii::info("使能测试", "login");
// var_dump(Yii::$app->log->targets["login"]->messages = ["test", "info", "login"]);die;
// die;

echo Markdown::widget(["content" => implode("\n", $html)]);

